package com.xuecheng.media.service.impl;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.vod.model.v20170321.CreateUploadVideoResponse;
import com.aliyuncs.vod.model.v20170321.GetPlayInfoResponse;
import com.aliyuncs.vod.model.v20170321.RefreshUploadVideoResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xuecheng.api.learning.model.vo.ResourceUrlModel;
import com.xuecheng.api.media.model.aliyun.VodUploadRequest;
import com.xuecheng.api.media.model.aliyun.VodUploadToken;
import com.xuecheng.api.media.model.dto.MediaDTO;
import com.xuecheng.api.media.model.qo.QueryMediaModel;
import com.xuecheng.common.domain.code.CommonErrorCode;
import com.xuecheng.common.domain.page.PageRequestParams;
import com.xuecheng.common.domain.page.PageVO;
import com.xuecheng.common.domain.response.RestResponse;
import com.xuecheng.common.enums.common.AuditEnum;
import com.xuecheng.common.enums.common.CommonEnum;
import com.xuecheng.common.enums.common.ResourceTypeEnum;
import com.xuecheng.common.exception.ExceptionCast;
import com.xuecheng.common.util.StringUtil;
import com.xuecheng.media.common.constant.MediaErrorCode;
import com.xuecheng.media.common.mediaurl.MediaUrl;
import com.xuecheng.media.common.utils.AliyunVODUtil;
import com.xuecheng.media.convert.MediaConvert;
import com.xuecheng.media.entity.Media;
import com.xuecheng.media.mapper.MediaMapper;
import com.xuecheng.media.service.MediaService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;

import javax.swing.plaf.synth.Region;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 媒资信息 服务实现类
 * </p>
 *
 * @author itcast
 */
@Slf4j
@Service
public class MediaServiceImpl extends ServiceImpl<MediaMapper, Media> implements MediaService {

    /*
    * aliyun.region = cn-beijing
        aliyun.accessKeyId = LTAI4G3wMNtzQPfFPDMHoCJF
        aliyun.accessKeySecret = uxzY3N4czSqZqhcXcuOq0ZcRlB6zjB
    * */

    @Value("${aliyun.region}")
    private String region;

    @Value("${aliyun.accessKeyId}")
    private String accessKeyId;

    @Value("${aliyun.accessKeySecret}")
    private String accessKeySecret;

    @Autowired
    private StringRedisTemplate redisTemplate;
    /*
     * 通过阿里云的视频点播的sdk来生成上传出凭证和地址
     *
     * 1.获得连接视频点播服务的客户端对象
     * 2.创建出请求对象
     * 3.获得响应数据
     * 4.获得结果数据并封装
     *
     * */
    public VodUploadToken createVODUploadToken(VodUploadRequest request) {
        // 只要业务服务需要连接外部的服务来获得数据，需要网络的不稳定因素
        // 代码操作时，需要通过try/catch来捕获不稳定因素导致的异常信息
        try {
            //1.获得连接视频点播服务的客户端对象
            DefaultAcsClient client = AliyunVODUtil.initVodClient(region, accessKeyId, accessKeySecret);

            //2.创建出请求对象
            //判断关键数据: 文件标题,文件名称
            String title = request.getTitle();
            String fileName = request.getFileName();

            if (StringUtil.isBlank(title) || StringUtil.isBlank(fileName)) {
                ExceptionCast.cast(CommonErrorCode.E_100101);
            }
            //3.获得响应数据
            CreateUploadVideoResponse response = AliyunVODUtil.createUploadVideo(client, title, fileName);
            //4.获得结果数据并封装
            VodUploadToken token = new VodUploadToken();
            token.setVideoId(response.getVideoId());
            token.setUploadAddress(response.getUploadAddress());
            token.setUploadAuth(response.getUploadAuth());
            token.setRequestId(response.getRequestId());
            return token;
        } catch (Exception e) {
            log.error("获得文件上传凭证失败: {} ", e.getMessage());
            ExceptionCast.castWithException(MediaErrorCode.E_140011, e);
            return null;
        }
    }

    /*
     * 通过阿里云的视频点播的sdk来生成上传出凭证和地址
     *
     * 1.获得连接视频点播服务的客户端对象
     * 2.创建出请求对象
     * 3.获得响应数据
     * 4.获得结果数据并封装
     *
     * */
    public VodUploadToken refreshVODUploadToken(String videoId) {
        try {
            //1.获得连接视频点播服务的客户端对象
            DefaultAcsClient client = AliyunVODUtil.initVodClient(region, accessKeyId, accessKeySecret);
            //2.创建出请求对象
            //判断关键数据
            if (StringUtil.isBlank(videoId)) {
                ExceptionCast.cast(CommonErrorCode.E_100101);
            }
            //3.获得响应数据
            RefreshUploadVideoResponse response = AliyunVODUtil.refreshUploadVideo(client, videoId);
            //4.获得结果数据并封装
            VodUploadToken token = new VodUploadToken();
            token.setVideoId(response.getVideoId());
            token.setUploadAuth(response.getUploadAuth());
            token.setUploadAddress(response.getUploadAddress());
            token.setRequestId(response.getRequestId());
            return token;
        } catch (Exception e) {
            log.error("刷新文件上传凭证失败: {} ", e.getMessage());
            ExceptionCast.castWithException(MediaErrorCode.E_140015, e);
            return null;
        }
    }

    /**
     * 1.判断关键数据
     * 前端操作无法确定关键数据
     * 通过数据库的表结构确定关键数据（项目中表是不能修改的）
     * filename  fileId
     * 判断保存的数据是必须有机构的数据companyId
     * 一个媒资要属于一个机构下
     * 2.保存媒资信息
     * 根据上传文件的类型来做不同的处理
     * 1.如果上传文件为视频，获得视频的播放地址
     * 2.如果上传文件为文档，获得文件的访问路径地址
     * 3.将保存后的数据转为dto并返回
     */
    @Transactional
    public MediaDTO createMedia(MediaDTO dto) {
        //1.判断关键数据
        if (StringUtil.isBlank(dto.getFileId()) ||
                StringUtil.isBlank(dto.getFilename()) ||
                ObjectUtils.isEmpty(dto.getCompanyId())) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }
        //2.保存媒资信息
        //dto-->po
        Media media = MediaConvert.INSTANCE.dto2entity(dto);
        // 如果是添加操作，数据特点为id为空
        media.setId(null);
        // 根据上传文件的类型来做不同的处理
        //   2.1如果上传文件为视频，获得视频的播放地址
        if (ResourceTypeEnum.VIDEO.getCode().equals(media.getType())) {
            // 通过工具类来获得视频播放地址
            try {
                DefaultAcsClient client = AliyunVODUtil.initVodClient(region, accessKeyId, accessKeySecret);

                GetPlayInfoResponse playInfo = AliyunVODUtil.getPlayInfo(client, media.getFileId());

                List<GetPlayInfoResponse.PlayInfo> playInfoList = playInfo.getPlayInfoList();

                if (ObjectUtils.isEmpty(playInfoList)) {
                    ExceptionCast.cast(MediaErrorCode.E_140012);
                } else {
                    // 如果媒资文件视频地址有多个，只会保存一个就可以
                    GetPlayInfoResponse.PlayInfo playInfo1 = playInfoList.get(0);

                    //将视频地址放到redis缓存中,并添加过期时间
                    redisTemplate.opsForValue().set(MediaUrl.MEDIA_URL_PREFIX + media.getFileId(),playInfo1.getPlayURL(),55,TimeUnit.MINUTES);

                    media.setUrl(playInfo1.getPlayURL());
                }
            } catch (Exception e) {
                log.error("获得视频播放地址失败：{}", e.getMessage());
                ExceptionCast.castWithException(MediaErrorCode.E_140012, e);
            }
        }
        //给新添加的媒资赋值审核状态(新添加的媒资为:未审核--数据库保存使用编码)
        media.setAuditStatus(AuditEnum.AUDIT_UNPAST_STATUS.getCode());
        //3.将保存后的数据转为dto并返回
        boolean save = this.save(media);
        if (!save) {
            ExceptionCast.cast(MediaErrorCode.E_140001);
        }
        MediaDTO mediaDTO = MediaConvert.INSTANCE.entity2dto(media);
        return mediaDTO;
    }

    /*
     * 业务分析：
     *   1.实现功能
     *     1.分页查询
     *       当前页
     *       每页条数
     *     2.条件查询
     *        根据课程名称      --模糊查询（like）
     *        根据课程审核状态   --精确查询（equals）
     *     2.将结果进行封装
     *      使用PageVO来封装结果数据
     *         总条数
     *         当前页
     *         每页条数
     *         当前页的集合数据
     *
     *   MP 封装mybatis
     *      -对dao（mapper）封装
     *      -对service封装:将mapper集成到service中，在service不需要将mapper注入
     *        业务接口类需要实现 mp 的接口 IService
     *        业务实现类需要集成 mp 的service的父类并实现接口
     * */
    public PageVO queryMediaList(PageRequestParams params, QueryMediaModel model, Long companyId) {
        //1.业务判断
        //判断分页数据
        Long pageNo = params.getPageNo();
        Integer pageSize = params.getPageSize();
        if (pageNo < 1) {
            pageNo = PageRequestParams.DEFAULT_PAGE_NUM;
        }
        if (pageSize < 1) {
            pageSize = PageRequestParams.DEFAULT_PAGE_SIZE;
        }

        //2.构建查询条件
        LambdaQueryWrapper<Media> queryWrapper = new LambdaQueryWrapper<>();
        //根据文件名称
        queryWrapper.like(StringUtil.isNotBlank(model.getFilename()), Media::getFilename, model.getFilename());
        //根据文件类型
        queryWrapper.eq(StringUtil.isNotBlank(model.getType()), Media::getType, model.getType());
        //添加对媒资审核状态的条件
        queryWrapper.eq(StringUtil.isNotBlank(model.getAuditStatus()), Media::getAuditStatus, model.getAuditStatus());
        //根据公司id
        if (!ObjectUtils.isEmpty(companyId)) {
            queryWrapper.eq(Media::getCompanyId, companyId);
        }
        //根据状态
        queryWrapper.eq(Media::getStatus, CommonEnum.USING_FLAG.getCodeInt());
        //构建分页对象数据
        Page<Media> mediaPage = new Page<>(pageNo, pageSize);
        //查询数据
        Page<Media> pageResult = this.page(mediaPage, queryWrapper);
        // 5.将结果数据进行封装==》PageVO
        List<Media> records = pageResult.getRecords();
        long total = pageResult.getTotal();
        //pos-->dtos
        List<MediaDTO> dtos = Collections.emptyList();
        if (!ObjectUtils.isEmpty(records)) {
            dtos = MediaConvert.INSTANCE.entitys2dtos(records);
        }

        PageVO<MediaDTO> pageVO = new PageVO<>(dtos, total, pageNo, pageSize);
        return pageVO;
    }

    /**
     * 1.判断关键数据
     * mediaId  companyId
     * 2.判断媒资信息
     * 判断媒资信息是否存在
     * 判断是否是同一家机构
     * 判断媒资信息是否已经删除
     * 3.将媒资文件的地址返回给前端
     */
    public String previewMedia(Long mediaId, Long companyId) {
        //1.判断关键数据
        if (ObjectUtils.isEmpty(mediaId)) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }
        //2.判断媒资信息
        Media media = this.getById(mediaId);
        //判断媒资信息是否存在
        if (ObjectUtils.isEmpty(media)) {
            ExceptionCast.cast(MediaErrorCode.E_140005);
        }
        if (!ObjectUtils.isEmpty(companyId)) {
            //判断是否是同一家机构
            if (!ObjectUtils.nullSafeEquals(media.getCompanyId(), companyId)) {
                ExceptionCast.cast(CommonErrorCode.E_100108);
            }
        }
        //判断媒资信息是否删除
        if (CommonEnum.DELETE_FLAG.getCode().equals(media.getStatus())) {
            ExceptionCast.cast(MediaErrorCode.E_140008);
        }


        //3.将媒资文件的地址返回给前端
        //先查询redis, 如果没有再向阿里云重新获取视频地址 并保存redis  数据库
        String url = redisTemplate.opsForValue().get(MediaUrl.MEDIA_URL_PREFIX + media.getFileId());
        if (StringUtil.isBlank(url)) {
            url = getPlayUrl(media);
            redisTemplate.opsForValue().set(MediaUrl.MEDIA_URL_PREFIX + media.getFileId(), url, 55, TimeUnit.MINUTES);

            //修改数据库url
            LambdaUpdateWrapper<Media> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(Media::getUrl, url);
            updateWrapper.eq(Media::getFileId, media.getFileId());
            boolean update = this.update(updateWrapper);
            if (!update) {
                ExceptionCast.cast(MediaErrorCode.E_140017);
            }
        }

        return url;
    }

    private String getPlayUrl(Media media) {
        try {
            DefaultAcsClient client = AliyunVODUtil.initVodClient(region, accessKeyId, accessKeySecret);
            GetPlayInfoResponse playInfo = AliyunVODUtil.getPlayInfo(client, media.getFileId());
            List<GetPlayInfoResponse.PlayInfo> playInfoList = playInfo.getPlayInfoList();
            if (ObjectUtils.isEmpty(playInfoList)) {
                ExceptionCast.cast(MediaErrorCode.E_140012);
            } else {
                // 如果媒资文件视频地址有多个，只会保存一个就可以
                GetPlayInfoResponse.PlayInfo playInfo1 = playInfoList.get(0);
                return playInfo1.getPlayURL();
            }
        } catch (Exception e) {
            log.error("获得视频播放地址失败：{}", e.getMessage());
            ExceptionCast.castWithException(MediaErrorCode.E_140012, e);
        }
        return null;
    }

    private void verifyPO(Long companyId, Media media) {
        //判断媒资信息是否存在
        if (ObjectUtils.isEmpty(media)) {
            ExceptionCast.cast(MediaErrorCode.E_140005);
        }
        //判断是否是同一家机构
        if (!ObjectUtils.nullSafeEquals(media.getCompanyId(), companyId)) {
            ExceptionCast.cast(CommonErrorCode.E_100108);
        }
        //判断媒资信息是否删除
        if (CommonEnum.DELETE_FLAG.getCode().equals(media.getStatus())) {
            ExceptionCast.cast(MediaErrorCode.E_140008);
        }
    }

    /**
     * 1.判断关键数据是否合法
     * mediaId  companyId
     * 2.根据id获得媒资信息并进行判断
     * 媒资信息是否存在
     * 是否是同一家机构
     * 判断媒资信息是否已经删除
     * 判断媒资信息审核状态
     * 3.删除数据(修改媒资信息状态)
     *   删除阿里云视频
     */
    @Transactional
    public void removeMedia(Long mediaId, Long companyId) {
        //1.判断关键数据
        if (ObjectUtils.isEmpty(mediaId) || ObjectUtils.isEmpty(companyId)) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }
        //2.根据id获得媒资信息并进行判断
        Media media = this.getById(mediaId);
        verifyPO(companyId, media);

        //判断媒资信息审核状态
        if (AuditEnum.AUDIT_PASTED_STATUS.getCode().equals(media.getAuditStatus())) {
            ExceptionCast.cast(MediaErrorCode.E_140009);
        }
        //3.删除数据(修改媒资信息状态)
        //修改媒资信息状态为删除态
        LambdaUpdateWrapper<Media> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(Media::getStatus, CommonEnum.DELETE_FLAG.getCode());
        updateWrapper.set(Media::getChangeDate, LocalDateTime.now());
        updateWrapper.eq(Media::getId, mediaId);
        boolean result = this.update(updateWrapper);
        if (!result) {
            ExceptionCast.cast(MediaErrorCode.E_140002);
        }
        try {
            //删除阿里云视频
            DefaultAcsClient client = AliyunVODUtil.initVodClient(region, accessKeyId, accessKeySecret);

            AliyunVODUtil.deleteVideo(client,media.getFileId());
        } catch (Exception e) {
            log.error("删除阿里云视频失败：{}", e.getMessage());
            ExceptionCast.castWithException(MediaErrorCode.E_140002, e);
        }
    }

    /**
     * 1.判断关键数据合法性
     * mediaId
     * 2.根据id获得媒资信息并判断
     * 媒资信息是否存在
     * 媒资信息是否已删除
     * 媒资信息审核状态(是否审核通过)
     *3.使用固定内容返回数据
     *不管成功和失败都要返回数据
     */
    public RestResponse<MediaDTO> getMediaById4s(Long mediaId) {
        //1.判断关键数据合法性
        if (ObjectUtils.isEmpty(mediaId)) {
            return RestResponse.validfail(CommonErrorCode.E_100101);
        }
        //2.根据id获得媒资信息并判断
        Media media = this.getById(mediaId);
        //媒资信息是否存在
        if (ObjectUtils.isEmpty(media)) {
            return RestResponse.validfail(MediaErrorCode.E_140005);
        }
        //媒资信息是否已删除
        if (CommonEnum.DELETE_FLAG.getCode().equals(media.getStatus())) {
            return RestResponse.validfail(MediaErrorCode.E_140008);
        }
        //媒资信息审核状态
        if (!AuditEnum.AUDIT_PASTED_STATUS.getCode().equals(media.getAuditStatus())) {
            return RestResponse.validfail(MediaErrorCode.E_140010);
        }
        //3.使用固定内容返回数据
        //po-->dto
        MediaDTO mediaDTO = MediaConvert.INSTANCE.entity2dto(media);
        RestResponse<MediaDTO> success = RestResponse.success(mediaDTO);
        return success;
    }

    /**
     * 1.判断关键数据合法性
     * 媒资id
     * 审核状态 auditStatus
     * 2.根据id 获得媒资信息并判断
     * 判断媒资信息是否存在
     * 判断媒资信息是否已删除
     * 判断审核状态(未审核的才可以审核)
     * 3.修改审核状态(通过 / 未通过)
     *
     */
    @Transactional
    public void auditMedia(MediaDTO dto) {

        //1.判断关键数据合法性
        if (ObjectUtils.isEmpty(dto.getId()) || StringUtil.isBlank(dto.getAuditStatus())) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }
        //2.根据id 获得媒资信息并判断
        Media media = this.getById(dto.getId());
        //判断媒资信息是否存在
        if (ObjectUtils.isEmpty(media)) {
            ExceptionCast.cast(MediaErrorCode.E_140005);
        }
        //判断媒资信息是否已删除
        if (CommonEnum.DELETE_FLAG.getCode().equals(media.getStatus())) {
            ExceptionCast.cast(MediaErrorCode.E_140008);
        }
        //判断审核状态(未审核的才可以审核)
        if (!AuditEnum.AUDIT_UNPAST_STATUS.getCode().equals(media.getAuditStatus())) {
            ExceptionCast.cast(MediaErrorCode.E_140010);
        }
        //3.修改审核状态(通过 / 未通过)
        LambdaUpdateWrapper<Media> updateWrapper = new LambdaUpdateWrapper<>();
        //审核状态
        updateWrapper.set(Media::getAuditStatus, dto.getAuditStatus());
        //审核意见
        updateWrapper.set(Media::getAuditMind, dto.getAuditMind());
        //修改时间
        updateWrapper.set(Media::getChangeDate, LocalDateTime.now());
        updateWrapper.eq(Media::getId, dto.getId());

        boolean update = this.update(updateWrapper);
        if (!update) {
            ExceptionCast.cast(MediaErrorCode.E_140016);
        }
    }

    /**
     * 1.判断关键数据
     * mediaId
     * 2.获得媒资信息并判断
     * 媒资信息是否存在
     * 3.获得媒资对应的资源路径
     * 判断媒资类型
     * 媒资类型:视频、文档、作业...
     * 4.封装数据并返回
     */
    public RestResponse<ResourceUrlModel> getMediaUrl(Long mediaId) {

        ResourceUrlModel resourceUrlModel = new ResourceUrlModel();
        //1.判断关键数据
        if (ObjectUtils.isEmpty(mediaId)) {
            return RestResponse.validfail(CommonErrorCode.E_100101);
        }
        //2.获得媒资信息并判断
        //媒资信息是否存在
        Media media = this.getById(mediaId);
        if (ObjectUtils.isEmpty(media)) {
            return RestResponse.validfail(MediaErrorCode.E_140005);
        }
        //3.获得媒资对应的资源路径
        //判断媒资类型
        if (ResourceTypeEnum.VIDEO.getCode().equals(media.getType())) {
            //视频
            //先查询redis, 如果没有再向阿里云重新获取视频地址 并保存redis
            String url = redisTemplate.opsForValue().get(MediaUrl.MEDIA_URL_PREFIX + media.getFileId());
            if (StringUtil.isBlank(url)) {
                try {
                    url = getPlayUrl(media);
                } catch (Exception e) {
                    log.error("获得视频播放地址失败：{}", e.getMessage());
                    return RestResponse.validfail(MediaErrorCode.E_140012);
                }
                redisTemplate.opsForValue().set(MediaUrl.MEDIA_URL_PREFIX + media.getFileId(), url, 55, TimeUnit.MINUTES);
            }

            resourceUrlModel.setPlayUrl(url);
        } else if (ResourceTypeEnum.DOCUMENT.getCode().equals(media.getType())) {
            //如果是文档
            // 获得文档的访问连接地址
        }
        //4.封装数据并返回

        return RestResponse.success(resourceUrlModel);
    }

}
